/* Copyright (c) 2000 James Archibald, Brigham Young University */ 
/* Modifications copyright (c) 2006 David A. Penry, Brigham Young University */
/* Extended by James Carroll to model an arbitrary number of cache layers */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// For cwd()
//#include <internal.h>
#include <dirent.h>
#include <errno.h>


#include "stalldef.h"	/* #defines in countstall parameters */ 
#include "ops.h"

#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif

#ifndef max
#define max(x,y) ((x)>(y)?(x):(y))
#endif

/* these specify the latencies used: will remain constant throughout
   all labs.  */
#define LATENCY_INT  1		
#define LATENCY_ADD  2
#define LATENCY_LOAD 1
#define LATENCY_MUL  5
#define LATENCY_DIV 19


/********* global variables declared in another file that you will need at
	   some point to complete the labs *************/
extern int verbose;			/* flag: set if verbose output expected */
extern int in_execution;	/* flag: set if currently executing */
extern int i_count;			/* number of instructions executed */
extern int nop_count;		/* number of nops executed */
extern char verbosestr[];	/* string output in verbose mode */
extern int branch_flag;		/* branch outcome flag */
extern int b_count;			/* number of branch instructions so far */
extern int j_count;			/* number of jump instructions so far */
extern int pc;				/* program counter */
extern unsigned long ir;        /* instruction register */
extern int newpc;               /* BTA for taken branches */
extern unsigned int ldstaddr;	/* ref address for loads&stores */

/********** globals that specify the simulated configuration ***********/

int latency[5];		/* latencies for all f_units, initialized with
					   #defines above */
//int mainMemoryLatency;       /* speed of memory accesses */

/******************* globals to track state of pipeline *****************/
unsigned long prevIFcycle;
unsigned long prevIDcycle;

/* global variables to store resource timestamps */
unsigned long IRstamps[32];	/* int regs */
unsigned long FRstamps[32];	/* float regs */
unsigned long DRstamps[32];	/* double regs */
unsigned long FPSstamps;		/* FP status bit updates */

/* global variables to store identity of last funit to write */
unsigned long IRwriter[32];	/* int regs */
unsigned long FRwriter[32];	/* float regs */
unsigned long DRwriter[32];	/* double regs */
unsigned long FPSwriter;		/* FP status bit */

/****** global variables defined here for statistics of interest ***********/

#define MAXUNITS 5
#define RAWindex 0
#define WAWindex 1
int totalstalls;		/* total stall count */
int specstalls[MAXUNITS][3];	/* specific stall counts, see tpressed
				 for interpretation */
unsigned long totalcycles;	/* total cycles required to execute */

int mylog2(int n);

/* handy global used to simplify output routine */
char *f_unit_name[5] = { "int", "load", "add", "mult", "div" };

// Typedefs and data structures
typedef struct cache_block_struct {
	int valid;
	int tag;
	int cycleStamp;	// used for LRU replacement policy
} cacheBlock;

typedef cacheBlock* Block;
typedef struct cache_control_block* Cache;

typedef struct cache_control_block {
	int numSets;
	int associativity;
	int hitTime;  // In processor cycles
	int blockSize;
	Block cache_array; // pointer to beginning of actual cache
	int missCount;
	int hitCount;
	int accessCount;
	int penaltyCount;
    Cache lowerCache;
} CacheCCB;


// Global Variables
CacheCCB icacheCCB;
CacheCCB dcacheCCB;
CacheCCB l2cacheCCB;
CacheCCB dramCCB;
CacheCCB nandCCB;
CacheCCB diskCCB;

Cache icache  = &icacheCCB;
Cache dcache  = &dcacheCCB;
Cache l2cache = &l2cacheCCB;
Cache dram = &dramCCB;
Cache nand = &nandCCB;
Cache disk = &diskCCB;

// Function Prototypes
int genericCacheAccess(int address, unsigned long cycle, Cache cache);
int cacheHit(int address, unsigned long cycle, Cache cache);
void timeStamp (int address, Cache cache);
void replacementPolicy (int address, unsigned long cycle, Cache cache);
void allocateCache();
void deallocateCache();

/******************* Initialization and options ************************/

void init_latencyCache(void) { // default caches
  latency[INT_UNIT] = LATENCY_INT;
  latency[ADD_UNIT] = LATENCY_ADD;
  latency[LOAD_UNIT] = LATENCY_LOAD;
  latency[MUL_UNIT] = LATENCY_MUL;
  latency[DIV_UNIT] = LATENCY_DIV;

  icache->numSets = 32;
  icache->associativity = 2;
  icache->blockSize = 16;
  icache->hitTime = 1;

  dcache->numSets = 4;
  dcache->associativity = 2;
  dcache->blockSize = 8;
  dcache->hitTime = 1;

  l2cache->numSets = 8;
  l2cache->associativity = 4;
  l2cache->blockSize = 16;
  l2cache->hitTime = 4;
 
  dram->numSets = 1;
  dram->associativity = 100;
  dram->blockSize = 100;
  dram->hitTime = 10;
 
  nand->numSets = 1;
  nand->associativity = 1000;
  nand->blockSize = 1000;
  nand->hitTime = 100;
  
  disk->numSets = 1;
  disk->associativity = 10000;
  disk->blockSize = 10000;
  disk->hitTime = 1000;
  
//  mainMemoryLatency = 50;
}

void init_latency(void) { // perfect caches
  latency[INT_UNIT] = LATENCY_INT;
  latency[ADD_UNIT] = LATENCY_ADD;
  latency[LOAD_UNIT] = LATENCY_LOAD;
  latency[MUL_UNIT] = LATENCY_MUL;
  latency[DIV_UNIT] = LATENCY_DIV;
  
  icache->numSets = 0;
  icache->associativity = 2;
  icache->blockSize = 32;
  icache->hitTime = 1;
  
  dcache->numSets = 0;
  dcache->associativity = 2;
  dcache->blockSize = 32;
  dcache->hitTime = 1;
  
  l2cache->numSets = 0;
  l2cache->associativity = 8;
  l2cache->blockSize = 64;
  l2cache->hitTime = 2;
  
  dram->numSets = 0;
  dram->associativity = 100;
  dram->blockSize = 100;
  dram->hitTime = 10;
 
  nand->numSets = 0;
  nand->associativity = 1000;
  nand->blockSize = 1000;
  nand->hitTime = 100;
  
  disk->numSets = 0;
  disk->associativity = 10000;
  disk->blockSize = 10000;
  disk->hitTime = 1000;
  
//  mainMemoryLatency = 1;
}

int handle_option(char *arg) {
  if (!strncmp(arg,"--lat:int=",10)) {
    latency[INT_UNIT] = atoi(arg+10);
  }
  else if (!strncmp(arg,"--lat:add=",10)) {
    latency[ADD_UNIT] = atoi(arg+10);
  }
  else if (!strncmp(arg,"--lat:load=",11)) {
    latency[LOAD_UNIT]=atoi(arg+11);
  }
  else if (!strncmp(arg,"--lat:mul=",10)) {
    latency[MUL_UNIT] = atoi(arg+10);
  }
  else if (!strncmp(arg,"--lat:div=",10)) {
    latency[DIV_UNIT] = atoi(arg+10);
  }
  else if (!strncmp(arg,"--icache=",9)) {
    sscanf(arg+9, "%d:%d:%d", &icache->numSets, &icache->associativity, &icache->blockSize);
  }
  else if (!strncmp(arg,"--dcache=",9)) {
    sscanf(arg+9, "%d:%d:%d", &dcache->numSets, &dcache->associativity, &dcache->blockSize);
  }
  else if (!strncmp(arg,"--l2cache=",10)) {
    sscanf(arg+10, "%d:%d:%d:%d", &l2cache->numSets, &l2cache->associativity, 
	   &l2cache->blockSize, &l2cache->hitTime);
  }
  else if (!strncmp(arg,"--dram=",7)) {
    sscanf(arg+10, "%d:%d:%d:%d", &dram->numSets, &dram->associativity, 
	   &dram->blockSize, &dram->hitTime);
  }
  else if (!strncmp(arg,"--nand=",7)) {
    sscanf(arg+10, "%d:%d:%d:%d", &nand->numSets, &nand->associativity, 
	   &nand->blockSize, &nand->hitTime);
  }
  else if (!strncmp(arg,"--disk=",7)) {
    sscanf(arg+10, "%d:%d:%d:%d", &disk->numSets, &disk->associativity, 
	   &disk->blockSize, &disk->hitTime);
  }
//  else if (!strncmp(arg,"--memspeed=",11)) {
//    mainMemoryLatency = atoi(arg+11);
//  }
  else if (!strncmp(arg,"--labCache",10)) {
    init_latencyCache();
  }
  else return 0;
  return 1;
}

void print_options(FILE *fp) {
  fprintf(fp," --labCache        Set parameters to Cache lab parameters\n");
  fprintf(fp," --lat:<unit>=<#>  Set latency of unit to #\n");
  fprintf(fp,"                   Units are int,load,add,mul,div\n");
  fprintf(fp, "--icache=sets:assoc:bsize      I-cache parameters\n");
  fprintf(fp, "--dcache=sets:assoc:bsize      D-cache parameters\n");
  fprintf(fp, "--l2cache=sets:assoc:bsize:hit L2-cache parameters\n");
  fprintf(fp, "--dram=sets:assoc:bsize:hit DRAM parameters\n");
  fprintf(fp, "--nand=sets:assoc:bsize:hit NAND parameters\n");
  fprintf(fp, "--disk=sets:assoc:bsize:hit HDD parameters\n");
//  fprintf(fp, "--memspeed=speed  Latency to memory\n");
}

int mylog2(int n)
{
  int cnt;
  if (n<=0) return -1;
  n--;
    for (cnt = 0; n; cnt++,n>>=1);
    return(cnt);
}

void clearstall(void)
{
    /* This function is called at the beginning of execution and it
       initializes all data structures used to track timing. */

    int i, iNumBlocks, dNumBlocks, l2NumBlocks, dramNumBlocks, nandNumBlocks, diskNumBlocks;
    for (i = 0; i < 32; i++)
    {
        IRstamps[i] = 0;
        FRstamps[i] = 0;
        DRstamps[i] = 0;
        IRwriter[i] = 0;
        FRwriter[i] = 0;
        DRwriter[i] = 0;
    }
    FPSstamps = 0;
    FPSwriter = 0;
    totalstalls = 0;
    totalcycles = 0;
    prevIFcycle = 0;
    prevIDcycle = 0;
    for (i = 0; i < MAXUNITS; i++)
    {
        specstalls[i][0] = 0;
        specstalls[i][1] = 0;
    }

    icache->accessCount = icache->missCount = icache->hitCount = 0;
    dcache->accessCount = dcache->missCount = dcache->hitCount = 0;
    l2cache->accessCount = l2cache->missCount = l2cache->hitCount = 0;
    dram->accessCount = dram->missCount = dram->hitCount = 0;
    nand->accessCount = nand->missCount = nand->hitCount = 0;
    disk->accessCount = disk->missCount = disk->hitCount = 0;

	// Invalidate the cache
	iNumBlocks = (icache->numSets) * (icache->associativity);
	for (i = 0; i < iNumBlocks; i++) { 
		icache->cache_array[i].valid = FALSE;	
	}
	
	dNumBlocks = (dcache->numSets) * (dcache->associativity);
	for (i = 0; i < dNumBlocks; i++) { 
		dcache->cache_array[i].valid = FALSE;	
	}
	
	l2NumBlocks = (l2cache->numSets) * (l2cache->associativity);
	for (i = 0; i < l2NumBlocks; i++) { 
		l2cache->cache_array[i].valid = FALSE;	
	}

	dramNumBlocks = (dram->numSets) * (dram->associativity);
	for (i = 0; i < dramNumBlocks; i++) { 
		dram->cache_array[i].valid = FALSE;	
	}

	nandNumBlocks = (nand->numSets) * (nand->associativity);
	for (i = 0; i < nandNumBlocks; i++) { 
		nand->cache_array[i].valid = FALSE;	
	}

	diskNumBlocks = (disk->numSets) * (disk->associativity);
	for (i = 0; i < diskNumBlocks; i++) { 
		disk->cache_array[i].valid = FALSE;	
	}

	return;

}

int L2cacheaccess(int addr, unsigned long cycle)
{
  return genericCacheAccess(addr, cycle, l2cache); 
}

int Icacheaccess(int addr, unsigned long cycle)
{
  return genericCacheAccess(addr, cycle, icache); 
}

int Dcacheaccess(int addr, unsigned long cycle)
{
  return genericCacheAccess(addr, cycle, dcache); 
}

/*
 * This function is intended to handle all cache accesses;  
 * The grandfather code given to me included functions for
 * each type of cache, so those still exist for historical
 * reasons.
 */
int genericCacheAccess(int address, unsigned long cycle, Cache cache)
{
    int penalty = 0;

    // Check for "non-existant" cache
    if (cache->numSets == 0) {
        if (cache->lowerCache == NULL) 
            return disk->hitTime;
        else
            return genericCacheAccess(address, cycle, cache->lowerCache);
    }

    cache->accessCount++;
    if (cacheHit(address, cycle, cache)) {
        cache->hitCount++;
        return cache->hitTime;
    } else {
        cache->missCount++;
        replacementPolicy(address,cycle,cache);
        if(cache->lowerCache == NULL) {
            penalty = disk->hitTime;
            cache->penaltyCount += penalty;
            return cache->hitTime + penalty;	
        } else {
            penalty = genericCacheAccess(address, cycle, cache->lowerCache);
            cache->penaltyCount += penalty;
            return cache->hitTime + penalty;	
        }
    }
    printf("ERROR: Reached unreachable code!\n");
}

/*
 * Determine if the word specificed by `address' is in the memory specified by
 * `cache'; if so, return TRUE; else return FALSE
 */
int cacheHit(int address, unsigned long cycle, Cache cache) {

	int blockOffsetBits = mylog2(cache->blockSize); 
	int blockAddr = ((unsigned)address) >> blockOffsetBits;
	int index = blockAddr % cache->numSets;
	int tag = ((unsigned)blockAddr) >> mylog2(cache->numSets);
	//int tag = address;
	int i;
	
	Block b;

	int blocksPerSet = cache->associativity;
	for (i=0; i < blocksPerSet; i++) {
		b = &(cache->cache_array[index*cache->associativity + i]);  
		if (b->valid && (b->tag == tag)) {
			b->cycleStamp = cycle;
			return TRUE;
		}	
	} 

	return FALSE;
}

/*
 * Implements a least-recently-used (LRU) replacement policy.
 * Load a the word specified by `address' into the memory specified by `cache'.
 * If the set is full, evict the LRU block.
 */
void replacementPolicy (int address, unsigned long cycle, Cache cache) {
	
	int blockOffsetBits = mylog2(cache->blockSize); 
	int blockAddr = ((unsigned)address) >> blockOffsetBits;
	int index = blockAddr % cache->numSets;
	int tag = ((unsigned)blockAddr) >> mylog2(cache->numSets);
	//int tag = address;
	int i;

	Block block, LRUblock;
	int blocksPerSet = cache->associativity;
	unsigned long minCycleStamp = cycle;
	
	// First check for an empty slot (only the first four accesses) 
	for (i=0; i < blocksPerSet; i++) {
		block = &(cache->cache_array[index * cache->associativity + i]);  
		if (block->valid == FALSE) {
			block->cycleStamp = cycle;
			block->tag = tag;
			block->valid = TRUE;
			return;
		}
	}

	// Set is full; find LRU and evict it
	for (i=0; i < blocksPerSet; i++) {
		block = &(cache->cache_array[index * cache->associativity + i]);  
		if (block->cycleStamp < minCycleStamp) {
			minCycleStamp = block->cycleStamp;
			LRUblock = block;
		}	
	}
	LRUblock->cycleStamp = cycle;
	LRUblock->tag = tag;
	LRUblock->valid = TRUE; // unnecessary
	return;		
}

void addstalls(int s1type, int src1, int s2type, int src2, int
		 funit, int rtype, int result)
{
    /*   s1type, s2type, rtype:   
	     NONE (0) => no operand
             INTR (1) => int register
	     FPR  (2) => float register (32 bits)
	     DPR  (3) => double register (64 bits)
	     FPS  (4) => fpstatus
	 src1, src2, result:
	         specifies register number 
	 funit:  the functional unit required by this instruction
	         (one of #defines in stalldef.h)
	           INT_UNIT
		   LOAD_UNIT
		   ADD_UNIT
		   MUL_UNIT
		   DIV_UNIT
		   NOP_UNIT (don't execute this instruction)
    */

    /* Here is the algorithm for the basic pipeline:

       1. Check for RAW and WAW hazards: determine latest time that a)
       source operands will be available, and b) that most recent
       write to dest register will be complete to avoid WAW hazard.
       (These are done by comparing the current time with each
       register's timestamp.)  If a RAW and WAW hazard exist on
       the same register, ignore the WAW hazard.

       If one or more hazards cause a stall, attribute entire stall to
       functional unit of latest hazard to clear.  In case of tie,
       attribute to src1 if involved, else src2.  (There isn't a
       terrific reason for this other than that we can match results
       exactly.)

       2. Having determined the issue cycle of the current
       instruction, update all timing variables to reflect the
       appropriate passage of time.

       3. Update the time stamp for the destination register (if any)
       with the last cycle on which the new value is NOT available */

    int max1,maxunit1,max2,maxunit2,max3,maxunit3,stalls;
    unsigned long BGcycle,IFcycle,IDcycle,EXcycle,MEMcycle,WBcycle; 
    char stallstr[200], tmpbuf[200];

    /* clear output strings if they will be used */
    if (verbose || in_execution) {
	verbosestr[0] = '\0';
	stallstr[0] = '\0';
    }

    /* now handle timing */

    BGcycle = prevIFcycle;

    /* for IF timing, cache is perfect with no.missCount, but can't go
       until previous instruction vacates IR (instruction register) */
    IFcycle = (prevIDcycle == 0) ? 1 : prevIDcycle;
    {
      int penalty = Icacheaccess(pc, IFcycle) - 1;
      if (penalty) {
	IFcycle += penalty;
	sprintf(tmpbuf," CM:(Fetch,%d)",penalty);
	strcat(stallstr,tmpbuf);
      }
    }

    prevIFcycle = IFcycle;
    IDcycle = IFcycle + 1;	/* first possible IDcycle */

    /* 1. check for RAW hazards */
    max1 = 0;
    switch (s1type)		/* check first operand  */
    {
    case NONE:
	break;
    case INTR:
	if (IRstamps[src1] > IDcycle)
	{
	    max1 = IRstamps[src1];
	    maxunit1 = IRwriter[src1];
	}
	break;
    case FPR:
	if (FRstamps[src1] > IDcycle)
	{
	    max1 = FRstamps[src1];
	    maxunit1 = FRwriter[src1];
	}
	break;
    case DPR:
	if (DRstamps[src1] > IDcycle)
	{
	    max1 = DRstamps[src1];
	    maxunit1 = DRwriter[src1];
	}
	break;
    case FPS:
	if (FPSstamps > IDcycle)
	{
	    max1 = FPSstamps;
	    maxunit1 = FPSwriter;
	}
	break;
    }

    max2 = 0;
    /* check second operand  */
    /* special case handling for stores */
    if (funit == LOAD_UNIT && s2type != NONE)
    {				/* this is a store */
	switch (s2type)
	{
	case INTR:
	    if (IRstamps[src2]-1 > IDcycle)
	    {
		max2 = IRstamps[src2]-1;
		maxunit2 = IRwriter[src2];
	    }
	    break;
	case FPR:
	    if (FRstamps[src2]-1 > IDcycle)
	    {
		max2 = FRstamps[src2]-1;
		maxunit2 = FRwriter[src2];
	    }
	    break;

	case DPR:
	    if (DRstamps[src2]-1 > IDcycle)
	    {
		max2 = DRstamps[src2]-1;
		maxunit2 = DRwriter[src2];
	    }
	    break;
	}
    }
    else
    {
	switch (s2type)		/* check second operand  */
	{
	case NONE:
	    break;
	case INTR:
	    if (IRstamps[src2] > IDcycle)
	    {
		max2 = IRstamps[src2];
		maxunit2 = IRwriter[src2];
	    }
	    break;
	case FPR:
	    if (FRstamps[src2] > IDcycle)
	    {
		max2 = FRstamps[src2];
		maxunit2 = FRwriter[src2];
	    }
	    break;
	case DPR:
	    if (DRstamps[src2] > IDcycle)
	    {
		max2 = DRstamps[src2];
		maxunit2 = DRwriter[src2];
	    }
	    break;
	case FPS:
	    if (FPSstamps > IDcycle)
	    {
		max2 = FPSstamps;
		maxunit2 = FPSwriter;
	    }
	    break;
	}
    }

    /* check for WAW hazards, but only if destination register != src
       regs */
    max3 = 0;
    if ((s1type != rtype || src1 != result) && 
	(s2type != rtype || src2 != result))
    {
	switch (rtype)		/* check destination register */
	{
	case NONE:
	    break;
	case INTR:
	    if (IRstamps[result] > IDcycle)
	    {
		max3 = IRstamps[result]+2;
		maxunit3 = IRwriter[result];
	    }
	    break;
	case FPR:
	    if (FRstamps[result] > IDcycle)
	    {
		max3 = FRstamps[result]+2;
		maxunit3 = FRwriter[result];
	    }
	    break;
	case DPR:
	    if (DRstamps[result] > IDcycle)
	    {
		max3 = DRstamps[result]+2;
		maxunit3 = DRwriter[result];
	    }
	    break;
	case FPS:
	    if (FPSstamps > IDcycle)
	    {
		max3 = FPSstamps+2;
		maxunit3 = FPSwriter;
	    }
	    break;
	}
    }

    /* 2. increment IDcycle if stall occurs, update statistics */ 
    if (max1 > 0 || max2 > 0 || max3 > 0)
    {				/* stall occurs */
	if (max2 > max1)	/* set max1 to longest src op */
	{
	    max1 = max2;
	    maxunit1 = maxunit2;
	}
	if (max3 > max1) 
	{
	  stalls = max3-IDcycle;
	  if (verbose || in_execution) {/* update output string */
	    sprintf(tmpbuf, " WAW:(%d, %s)", stalls, f_unit_name[maxunit3]);
	    strcat(stallstr,tmpbuf);
	  }
	  specstalls[maxunit3][WAWindex] += stalls;
	  totalstalls += stalls;
	  IDcycle = max3;
	}
	else
	{
	  stalls = max1-IDcycle;
	  if (verbose || in_execution) { /* update stall string */
	    sprintf(tmpbuf, " RAW:(%d, %s)", stalls, f_unit_name[maxunit1]);
	    strcat(stallstr,tmpbuf);
	  }
	  specstalls[maxunit1][RAWindex] += stalls;
	  totalstalls += stalls;
	  IDcycle = max1;
	}
    }
    else {	/* no stall occurs and IDcycle is okay */
    }
    
    /* wrap up stage timings */
    prevIDcycle = IDcycle;
    if (funit == NOP_UNIT)
	EXcycle = IDcycle + 1;	/* nops + traps*/
    else
	EXcycle = IDcycle + latency[funit];

    if (rtype != NONE && funit == LOAD_UNIT) { // handle loads
      int penalty = Dcacheaccess(ldstaddr, EXcycle) - 1;
      MEMcycle = penalty + 1 + EXcycle;
      if (penalty) { /* miss on load */
	sprintf(tmpbuf," CM:(Load,%d)",penalty);
	strcat(stallstr, tmpbuf);
      }
    } else {
      MEMcycle = EXcycle + 1;	/* perfect D-cache: never.missCount */
    }

    WBcycle = MEMcycle + 1;
    totalcycles = IDcycle+1;

    /* 3. update result register timestamp */
    switch (rtype)
    {
    case NONE:
	break;
    case INTR:
	IRstamps[result] = funit == LOAD_UNIT ? MEMcycle : EXcycle;
	IRwriter[result] = funit;
	break;
    case FPR:
	FRstamps[result] = funit == LOAD_UNIT ? MEMcycle : EXcycle;
	FRwriter[result] = funit;
	break;
    case DPR:
	DRstamps[result] = funit == LOAD_UNIT ? MEMcycle : EXcycle;
	DRwriter[result] = funit;
	break;
    case FPS:
	FPSstamps = EXcycle;
	FPSwriter = funit;
	break;
    }

    /* generate verbose output if desired */
    if (verbose || in_execution)
    {
	sprintf(verbosestr,"[%5lu IF+%2lu ID+%2lu (%5lu) EX+%2lu MEM+%2lu]%s",
		BGcycle,IFcycle-BGcycle,IDcycle-IFcycle,IDcycle,
		EXcycle-IDcycle, MEMcycle-EXcycle, stallstr);
    }
}

void tpressed(void)
{
    /* This function is called when the user presses the 't' key at
       the mydlx> prompt.  It prints timing information, including
       classifications of stall types to give you some idea what you
       should speed up to get better performance.  As the pipeline
       changes, the possible causes of stalls also change, so this
       routine and the data structures it accesses need to be
       updated. */

    if (totalcycles == 0)
    {
        printf("  No instructions executed\n");
        return;
    }
    printf("  Stall cycles\n");
    printf("    ld/store: RAW        %10d  [%2d%%]\n",
            specstalls[LOAD_UNIT][RAWindex],
            (int)(specstalls[LOAD_UNIT][RAWindex]/(totalcycles/100.0))); 
    printf("              WAW        %10d  [%2d%%]\n",
            specstalls[LOAD_UNIT][WAWindex],
            (int)(specstalls[LOAD_UNIT][WAWindex]/(totalcycles/100.0))); 
    printf("    FP add:   RAW        %10d  [%2d%%]\n",
            specstalls[ADD_UNIT][RAWindex],
            (int)(specstalls[ADD_UNIT][RAWindex]/(totalcycles/100.0))); 
    printf("              WAW        %10d  [%2d%%]\n",
            specstalls[ADD_UNIT][WAWindex],
            (int)(specstalls[ADD_UNIT][WAWindex]/(totalcycles/100.0))); 
    printf("    FP mult:  RAW        %10d  [%2d%%]\n",
            specstalls[MUL_UNIT][RAWindex],
            (int)(specstalls[MUL_UNIT][RAWindex]/(totalcycles/100.0))); 
    printf("              WAW        %10d  [%2d%%]\n",
            specstalls[MUL_UNIT][WAWindex],
            (int)(specstalls[MUL_UNIT][WAWindex]/(totalcycles/100.0))); 
    printf("    FP div:   RAW        %10d  [%2d%%]\n",
            specstalls[DIV_UNIT][RAWindex],
            (int)(specstalls[DIV_UNIT][RAWindex]/(totalcycles/100.0))); 
    printf("              WAW        %10d  [%2d%%]\n",
            specstalls[DIV_UNIT][WAWindex],
            (int)(specstalls[DIV_UNIT][WAWindex]/(totalcycles/100.0))); 
    printf("  Total stalls:          %10d  [%2d%%]\n",
            totalstalls, (int)(totalstalls/(totalcycles/100.0)));
    printf("  Total nops:            %10d  [%2d%%]\n",
            nop_count,(int)(nop_count/(totalcycles/100.0)));
    printf("  Total wasted cycles:   %10d  [%2d%%]\n",
            nop_count+totalstalls,(int)((nop_count+totalstalls) /
                (totalcycles/100.0)));  
    printf("  Useful instr:          %10d \n",i_count-nop_count);
    printf("  Total cycles:          %10d \n",totalcycles);
    printf("  CPI:                   %10.2f\n",(double) totalcycles /
            (i_count-nop_count)); 

    if (icache->numSets) {
        printf("  total I-cache accesses:  %10d\n", icache->accessCount);
        printf("  total I-cache misses:    %10d\n", icache->missCount);
        printf("  total I-cache hits:      %10d\n", icache->hitCount);
        printf("  total I-cache penalties: %10d\n", icache->penaltyCount);
        if (icache->accessCount > 0)
        {
            printf("  I-cache miss rate:   %17.2f%%\n",
                    100.0*(double)icache->missCount/(double)icache->accessCount);
            printf("  avg I-cache access time: %13.2f cycles\n",
                    (double)((icache->penaltyCount + icache->accessCount))/icache->accessCount);
        }
        else
        {
            printf("  I-cache miss rate:                -\n");
            printf("  avg I-cache access time:          -\n");
        }
    } 

    if (dcache->numSets) {
        printf("  total D-cache accesses:  %10d\n", dcache->accessCount);
        printf("  total D-cache misses:    %10d\n", dcache->missCount);
        printf("  total D-cache hits:      %10d\n", dcache->hitCount);
        printf("  total D-cache penalties: %10d\n", dcache->penaltyCount);
        if (dcache->accessCount > 0)
        {
            printf("  D-cache miss rate:   %17.2f%%\n",
                    100.0*(double)dcache->missCount/(double)dcache->accessCount);
            printf("  avg D-cache access time: %13.2f cycles\n",
                    (double)((dcache->penaltyCount + dcache->accessCount))/dcache->accessCount);
        }
        else
        {
            printf("  D-cache miss rate:                -\n");
            printf("  avg D-cache access time:          -\n");
        }
    }

    if (l2cache->numSets) {
        printf("  total L2-cache accesses: %10d\n", l2cache->accessCount);
        printf("  total L2-cache misses:   %10d\n", l2cache->missCount);
        printf("  total L2-cache hits:     %10d\n", l2cache->hitCount);
        printf("  total L2-cache penalties:%10d\n", l2cache->penaltyCount);
        if (l2cache->accessCount > 0)
        {
            printf("  L2-cache miss rate:  %17.2f%%\n",
                    100.0*(double)l2cache->missCount/(double)l2cache->accessCount);
            printf("  avg L2-cache access time:%13.2f cycles\n",
                    (double)((l2cache->penaltyCount + l2cache->hitTime * l2cache->accessCount))
                    / l2cache->accessCount);
        }
        else
        {
            printf("  L2-cache miss rate:               -\n");
            printf("  avg L2-cache access time:         -\n");
        }
    }
    if (dram->numSets) {
        printf("  total dram accesses: %10d\n", dram->accessCount);
        printf("  total dram misses:   %10d\n", dram->missCount);
        printf("  total dram hits:     %10d\n", dram->hitCount);
        printf("  total dram penalties:%10d\n", dram->penaltyCount);
        if (dram->accessCount > 0)
        {
            printf("  dram miss rate:  %17.2f%%\n",
                    100.0*(double)dram->missCount/(double)dram->accessCount);
            printf("  avg dram access time:%13.2f cycles\n",
                    (double)((dram->penaltyCount + dram->hitTime * dram->accessCount))
                    / dram->accessCount);
        }
        else
        {
            printf("  dram miss rate:               -\n");
            printf("  avg dram access time:         -\n");
        }
    }
    if (nand->numSets) {
        printf("  total nand accesses: %10d\n", nand->accessCount);
        printf("  total nand misses:   %10d\n", nand->missCount);
        printf("  total nand hits:     %10d\n", nand->hitCount);
        printf("  total nand penalties:%10d\n", nand->penaltyCount);
        if (nand->accessCount > 0)
        {
            printf("  nand miss rate:  %17.2f%%\n",
                    100.0*(double)nand->missCount/(double)nand->accessCount);
            printf("  avg nand access time:%13.2f cycles\n",
                    (double)((nand->penaltyCount + nand->hitTime * nand->accessCount))
                    / nand->accessCount);
        }
        else
        {
            printf("  nand miss rate:               -\n");
            printf("  avg nand access time:         -\n");
        }
    }
    if (disk->numSets) {
        printf("  total disk accesses: %10d\n", disk->accessCount);
        printf("  total disk misses:   %10d\n", disk->missCount);
        printf("  total disk hits:     %10d\n", disk->hitCount);
        printf("  total disk penalties:%10d\n", disk->penaltyCount);
        if (disk->accessCount > 0)
        {
            printf("  disk miss rate:  %17.2f%%\n",
                    100.0*(double)disk->missCount/(double)disk->accessCount);
            printf("  avg disk access time:%13.2f cycles\n",
                    (double)((disk->penaltyCount + disk->hitTime * disk->accessCount))
                    / disk->accessCount);
        }
        else
        {
            printf("  disk miss rate:               -\n");
            printf("  avg disk access time:         -\n");
        }
    }
}

/*
 * Print the same values as tpressed(), but in CSV format
 */
void tpressed_csv(void)
{
    if (totalcycles == 0)
    {
        printf("  No instructions executed\n");
        return;
    }
    printf("%10d,%2d%%,",
            specstalls[LOAD_UNIT][RAWindex],
            (int)(specstalls[LOAD_UNIT][RAWindex]/(totalcycles/100.0))); 
    printf("%10d,%2d%%,",
            specstalls[LOAD_UNIT][WAWindex],
            (int)(specstalls[LOAD_UNIT][WAWindex]/(totalcycles/100.0))); 
    printf("%10d,%2d%%,",
            specstalls[ADD_UNIT][RAWindex],
            (int)(specstalls[ADD_UNIT][RAWindex]/(totalcycles/100.0))); 
    printf("%10d,%2d%%,",
            specstalls[ADD_UNIT][WAWindex],
            (int)(specstalls[ADD_UNIT][WAWindex]/(totalcycles/100.0))); 
    printf("%10d,%2d%%,",
            specstalls[MUL_UNIT][RAWindex],
            (int)(specstalls[MUL_UNIT][RAWindex]/(totalcycles/100.0))); 
    printf("%10d,%2d%%,",
            specstalls[MUL_UNIT][WAWindex],
            (int)(specstalls[MUL_UNIT][WAWindex]/(totalcycles/100.0))); 
    printf("%10d,%2d%%,",
            specstalls[DIV_UNIT][RAWindex],
            (int)(specstalls[DIV_UNIT][RAWindex]/(totalcycles/100.0))); 
    printf("%10d,%2d%%,",
            specstalls[DIV_UNIT][WAWindex],
            (int)(specstalls[DIV_UNIT][WAWindex]/(totalcycles/100.0))); 
    printf("%10d,%2d%%,",
            totalstalls, (int)(totalstalls/(totalcycles/100.0)));
    printf("%10d,%2d%%,",
            nop_count,(int)(nop_count/(totalcycles/100.0)));
    printf("%10d,%2d%%,",
            nop_count+totalstalls,(int)((nop_count+totalstalls) /
                (totalcycles/100.0)));  
    printf("%10d,",i_count-nop_count);
    printf("%10d,",totalcycles);
    printf("%10.2f,",(double) totalcycles /
            (i_count-nop_count)); 

    if (icache->numSets) {
        printf("%10d,", icache->accessCount);
        printf("%10d,", icache->missCount);
        printf("%10d,", icache->hitCount);
        printf("%10d,", icache->penaltyCount);
        if (icache->accessCount > 0)
        {
            printf("%17.2f%%,",
                    100.0*(double)icache->missCount/(double)icache->accessCount);
            printf("%13.2f,",
                    (double)((icache->penaltyCount + icache->accessCount))/icache->accessCount);
        }
        else
        {
            printf(",");
            printf(",");
        }
    } 

    if (dcache->numSets) {
        printf("%10d,", dcache->accessCount);
        printf("%10d,", dcache->missCount);
        printf("%10d,", dcache->hitCount);
        printf("%10d,", dcache->penaltyCount);
        if (dcache->accessCount > 0)
        {
            printf("%17.2f%%,",
                    100.0*(double)dcache->missCount/(double)dcache->accessCount);
            printf("%13.2f,",
                    (double)((dcache->penaltyCount + dcache->accessCount))/dcache->accessCount);
        }
        else
        {
            printf(",");
            printf(",");
        }
    }

    if (l2cache->numSets) {
        printf("%10d,", l2cache->accessCount);
        printf("%10d,", l2cache->missCount);
        printf("%10d,", l2cache->hitCount);
        printf("%10d,", l2cache->penaltyCount);
        if (l2cache->accessCount > 0)
        {
            printf("%17.2f%%,",
                    100.0*(double)l2cache->missCount/(double)l2cache->accessCount);
            printf("%13.2f,",
                    (double)((l2cache->penaltyCount + l2cache->hitTime * l2cache->accessCount))
                    / l2cache->accessCount);
        }
        else
        {
            printf(",");
            printf(",");
        }
    }
    if (dram->numSets) {
        printf("%10d,", dram->accessCount);
        printf("%10d,", dram->missCount);
        printf("%10d,", dram->hitCount);
        printf("%10d,", dram->penaltyCount);
        if (dram->accessCount > 0)
        {
            printf("%17.2f%%,",
                    100.0*(double)dram->missCount/(double)dram->accessCount);
            printf("%13.2f,",
                    (double)((dram->penaltyCount + dram->hitTime * dram->accessCount))
                    / dram->accessCount);
        }
        else
        {
            printf(",");
            printf(",");
        }
    }
    if (nand->numSets) {
        printf("%10d,", nand->accessCount);
        printf("%10d,", nand->missCount);
        printf("%10d,", nand->hitCount);
        printf("%10d,", nand->penaltyCount);
        if (nand->accessCount > 0)
        {
            printf("%17.2f%%,",
                    100.0*(double)nand->missCount/(double)nand->accessCount);
            printf("%13.2f,",
                    (double)((nand->penaltyCount + nand->hitTime * nand->accessCount))
                    / nand->accessCount);
        }
        else
        {
            printf(",");
            printf(",");
        }
    }
    if (disk->numSets) {
        printf("%10d,", disk->accessCount);
        printf("%10d,", disk->missCount);
        printf("%10d,", disk->hitCount);
        printf("%10d,", disk->penaltyCount);
        if (disk->accessCount > 0)
        {
            printf("%17.2f%%,",
                    100.0*(double)disk->missCount/(double)disk->accessCount);
            printf("%13.2f,",
                    (double)((disk->penaltyCount + disk->hitTime * disk->accessCount))
                    / disk->accessCount);
        }
        else
        {
            printf(",");
            printf(",");
        }
    }
    printf("\n");
}

/*
 * Print the header row for a CSV output.  To be used before tpressed_csv()
 */
void tpressed_header(void)
{
    printf("Stall cycles,");
    printf("ld/store: RAW,");
    printf("ld/store: WAW,");
    printf("FP add: RAW,");       
    printf("FP add: WAW,");    
    printf("FP mult: RAW,");      
    printf("FP mult: WAW,");   
    printf("FP div: RAW,");
    printf("FP div: WAW,");
    printf("Total stalls,");
    printf("Total nops,");
    printf("Total wasted cycles,");
    printf("Useful instr,");
    printf("Total cycles,");
    printf("CPI,");

    if (icache->numSets) {
        printf("I-cache accesses,");
        printf("I-cache misses,");  
        printf("I-cache hits,");    
        printf("I-cache penalties,");
        printf("I-cache miss rate,");
        printf("avg I-cache access time (cycles),");
    } 
    if (dcache->numSets) {
        printf("D-cache accesses,");
        printf("D-cache misses,");  
        printf("D-cache hits,");    
        printf("D-cache penalties,");
        printf("D-cache miss rate,");
        printf("avg D-cache access time (cycles),");
    } 
    if (l2cache->numSets) {
        printf("D-cache accesses,");
        printf("D-cache misses,");  
        printf("D-cache hits,");    
        printf("D-cache penalties,");
        printf("D-cache miss rate,");
        printf("avg D-cache access time (cycles),");
    } 
    if (dram->numSets) {
        printf("dram accesses,");
        printf("dram misses,");  
        printf("dram hits,");    
        printf("dram penalties,");
        printf("dram miss rate,");
        printf("avg dram access time (cycles),");
    } 
    if (nand->numSets) {
        printf("nand accesses,");
        printf("nand misses,");  
        printf("nand hits,");    
        printf("nand penalties,");
        printf("nand miss rate,");
        printf("avg nand access time (cycles),");
    } 
    if (disk->numSets) {
        printf("disk accesses,");
        printf("disk misses,");  
        printf("disk hits,");    
        printf("disk penalties,");
        printf("disk miss rate,");
        printf("avg disk access time (cycles),");
    } 
    printf("\n");
}

void ppressed(void)
{
    /* This function is called when the user presses the 'p' key at
       the mydlx> prompt.  It prints the current CPU configuration.
       As simulated configurations increase in complexity, it should
       be extended to output all relevant CPU parameters.  */

    printf("--Basic pipeline configuration--\n");
    printf("      UNIT     LATENCY\n");
    printf("  Integer unit:  %2d\n", latency[INT_UNIT]);
    printf("  Load unit:     %2d\n", latency[LOAD_UNIT]);
    printf("  FP adder:      %2d\n", latency[ADD_UNIT]);
    printf("     multiplier: %2d\n", latency[MUL_UNIT]);
    printf("     divider:    %2d\n", latency[DIV_UNIT]);

    if (icache->numSets) {
      printf("I-cache:  %d sets, assoc=%d, blocksize=%d bytes\n",
	     icache->numSets, icache->associativity, icache->blockSize);
    }
    if (dcache->numSets) {
      printf("D-cache:  %d sets, assoc=%d, blocksize=%d bytes\n",
	     dcache->numSets, dcache->associativity, dcache->blockSize);
    }
    if (l2cache->numSets) {
      printf("L2-cache:  %d sets, assoc=%d, blocksize=%d bytes, "
	     "hit time=%d\n",
	     l2cache->numSets, l2cache->associativity, l2cache->blockSize, l2cache->hitTime);
    }
    if (dram->numSets) {
      printf("dram:  %d sets, assoc=%d, blocksize=%d bytes, "
	     "hit time=%d\n",
	     dram->numSets, dram->associativity, dram->blockSize, dram->hitTime);
    }
    if (nand->numSets) {
      printf("nand:  %d sets, assoc=%d, blocksize=%d bytes, "
	     "hit time=%d\n",
	     nand->numSets, nand->associativity, nand->blockSize, nand->hitTime);
    }
    if (disk->numSets) {
      printf("disk:  %d sets, assoc=%d, blocksize=%d bytes, "
	     "hit time=%d\n",
	     disk->numSets, disk->associativity, disk->blockSize, disk->hitTime);
    }

//    printf("main memory latency: %2d\n", mainMemoryLatency);

}

void fatalerrormsg(void)
{
    /* This function is called when any sort of run-time error is
       detected and execution is terminated.  It simply prints out the
       cycle count (maintained only in stall.c code) to help the user
       determine what happened and when.  */
    fprintf(stderr,"Current cycle: %lu\n",totalcycles);
}

void zpressed(void)
{
    /* This function is called when the user presses the 'z' key at
       the prompt.  It will be used in a later lab to dump branch
       prediction info and can be ignored until then.  It can be used
       in the meantime to print anything you want (for debugging
       purposes) whenever 'z' is pressed. */
	char buf[BUFSIZ + 1];

    // Call get current working directory and test for success
    // If successful, print current working directory
	if (getcwd(buf, sizeof(buf)) == NULL)
		printf("getcwd failed.\n");
    else
        printf("cwd: %s\n", buf);
    //printf("Function not implemented.\n");
	return;
}

/*
 * Allocate (malloc) memory for the three types of caches. Called
 * exactly once per execution of the program.
 */
void allocateCache() {
    int inumBlocks = icache->associativity * icache->numSets;
    int dnumBlocks = dcache->associativity * dcache->numSets;
    int l2numBlocks = l2cache->associativity * l2cache->numSets;
    int dramnumBlocks = dram->associativity * dram->numSets;
    int nandnumBlocks = nand->associativity * nand->numSets;
    int disknumBlocks = disk->associativity * disk->numSets;

    icache->cache_array = malloc(sizeof(cacheBlock)*inumBlocks);
    dcache->cache_array = malloc(sizeof(cacheBlock)*dnumBlocks);
    l2cache->cache_array = malloc(sizeof(cacheBlock)*l2numBlocks);
    dram->cache_array = malloc(sizeof(cacheBlock)*dramnumBlocks);
    nand->cache_array = malloc(sizeof(cacheBlock)*nandnumBlocks);
    disk->cache_array = malloc(sizeof(cacheBlock)*disknumBlocks);

    icache->lowerCache = l2cache;
    dcache->lowerCache = l2cache;
    l2cache->lowerCache = dram;
    dram->lowerCache = nand;
    nand->lowerCache = disk;
    disk->lowerCache = NULL;
    // In many cases, even the disk acts as a cache of network data.
    // For our experiment, we simply model the disk as a random-access
    // (constant-latency) storage element.  This is, of course, a 
    // great simplification.

    return;
}

/*
 * Deallocate (free) memory for the three types of caches. Called 
 * exactly once per execution of the program.
 */
void deallocateCache() {
	free (icache->cache_array);
	free (dcache->cache_array);
	free (l2cache->cache_array);
	free (dram->cache_array);
	free (nand->cache_array);
	free (disk->cache_array);
}
